<!--
Copyright 2023 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

<html>
    <head>
    <link rel="stylesheet" href="logica_syntax.css" type="text/css"/>
    <title>Logica | Good Robot</title>
    <link rel="icon" href="Logica_Icon.png" type="image/png">

    </head>

    <style>
        body {
          transform: scale(1);
        }
        canvas {
            border-width: 1px;
            border: solid;
            border-color: #000;
            box-shadow: 0px 0px 10px rgba(0, 200, 0, 0.3);
            border-radius: 10px;
        }
        .plain_code {
            margin-top: 10px;
            outline-color: #222;
            border: solid 3px black;
            width: 200px;
            height: 200px;
        }
        body {
            background-color: #010;
        }
        button, .buttony_href {
            font-family: 'Roboto Mono', monospace;
            border: solid;
            background-color: 0;
            color: #afa;
            padding: 10px;
            padding-top: 5px;
            padding-bottom: 5px;
            border-radius: 5px;
            text-decoration: none;
        }
        button:hover, .buttony_href:hover {
            background-color: #191;
            color: black;
        }
        button:active, .firmware_update_quazy_active, .buttony_href:active {
            background-color: #5f5;
            color: black;
        }
        @keyframes flip-anime {
          0% {transform: rotateZ(0deg);}
          10% {transform: rotateZ(10deg) scale(1.2); box-shadow: 5px 20px 40px rgba(0, 0, 0, 0.5);}
          40% {transform: rotateZ(10deg) scale(1.2); box-shadow: 5px 20px 40px rgba(0, 0, 0, 0.5);}
          50% {transform: rotateZ(-7deg) scale(1.3); box-shadow: 5px 20px 70px rgba(0, 0, 0, 0.5);}
          80% {transform: rotateZ(-7deg) scale(1.3); box-shadow: 5px 20px 70px rgba(0, 0, 0, 0.5);}
          100% {transform: rotateZ(0deg);}
        }

        .animated_flip {
          animation: flip-anime 0.7s;
          animation-timing-function: linear;
        }

    </style>
<body>
    <div style="display: flex; flex-flow: row;">
        <canvas id="canvas" width="500px" height="500px">
        </canvas>
        <div style="display: block; width: 800px; height: 300px; margin-left: 30px;">
          <div id="robot_code" class="code"  contenteditable="true" style="height: 430px">
# Control Good Robot with predicate RobotDesire.
# First columns must be:
# drive - seed of driving,
# turn - speed of turning and
# memory - what to overwrite memory with.
# You can have extra arguments as a dashboard for yourself.
# Use predicates RadarBeam for radar data and Memory for memory data.
# Use predicate SpellBearing to orient towards spells.
# Ctrl-Enter to update robot firmware with your current program state.
# Collect the spells and escape the forest! Good luck!
SafeMemory(safe_memory) :-
  Memory(memory),
  safe_memory = (
    if Length(memory) > 0 && Substr(memory, 1, 1) == "{" then
      memory
    else
      {time: 1, annoyance: 0}
  );

RobotDesire(
    drive: Format("%.3f", drive), 
    turn: Format("%.3f", turn), memory:, num_bearings:, bearing: bd) :-
  RadarBeam(direction: 0, distance:),
  drive = (
    if smell < 1 then
      2 * (distance - 80) / 100.0
    else
      (distance - 20) / 200.0
  ),
  x Avg= (d * dir / 10 :- RadarBeam(distance: d, direction: dir)),
  smell = (
    if bearing_distance < 200 then
      3
    else if bearing_distance < 600 then
      1
    else 0.2
  ),
  turn = x + wander + smell * bearing,
  horizon Max= (d :- RadarBeam(distance: d)),
  wander = Sin(time / 10) * annoyance,
  SafeMemory({time:, annoyance:}),
  new_annoyance = (
    if Abs(drive) < 0.1 then
      annoyance + 0.4
    else
      Greatest(0, annoyance - 1.0)
  ),
  memory = {time: time + 1, annoyance: new_annoyance},
  bd ArgMin= ((b -> d) -> d :- SpellBearing(bearing: b, distance: d)),
  (bearing -> bearing_distance) = bd,
  num_bearings += (1 :- SpellBearing());
          </div>

          <div style="display: flex; flex-flow:row;">
             <button id="firmware_update" onclick="digest()">FIRMWARE UPDATE</button>
             <button id="reset_game" onclick="restartGame()" style="margin-left:10px">RESTART GAME</button>
             <button id="copy_program" onclick="copyProgram()" style="margin-left:10px">COPY PROGRAM</button>
             <a href="https://twitter.com/intent/tweet?via=LogicaLang&hashtags=LogicalRobot,Logica&text=I used Logica to help Good Robot escape Dark Forest in 1000 seconds! Level 1 complete!%0Alogica.dev/robot.html%0A" style="margin-left: 10px; display: none" class="buttony_href" target="_blank" id="victory_href">VICTORY! TWEET ABOUT VICTORY!</a>
          </div>

          <script>
            var victoryHappened = false;
            var gameTime = 0;
            function monitorVictory() {
                if (victoryHappened) {
                    return false;
                }
                gameTime += 1;
                let d = distance(robot.x, robot.y, 0, 0);
                if (d > 4000 * 1.5) {
                    victoryHappened = true;
                    let a = document.getElementById('victory_href');
                    a.href = a.href.replace(/in%20\d*%20seconds/, 'in%20' + gameTime + '%20seconds');
                    a.style['display'] = 'inline';
                }
            }
            function restartGame() {
                robot.x = 0;
                robot.y = 0;
                victoryHappened = false;
                gameTime = 0;
                let a = document.getElementById('victory_href');
                a.style['display'] = 'none';
            }
            setInterval(monitorVictory, 1000);
           
          </script>

          <div id="robot_output" class="code"  contenteditable="false" style="height: 250px">
Loading Logical program...
          </div>

          <script>
            var worker = new Worker('logica.js');

            var backlogCount = 0;

            document.addEventListener('keydown', function(event) {
                if (event.ctrlKey && event.key === 'Enter') {
                    console.log('Ctrl+Enter was pressed');
                    let button = document.getElementById('firmware_update');
                    button.classList.add('firmware_update_quazy_active');
                    digest();
                    setTimeout(() => {
                        button.classList.remove('firmware_update_quazy_active');
                    }, 500);
                }
            });

            let desire_balance = 0;
            let regularized_desire_balance = 10;
            function requestDesire2() {
                if (theSQL == 'BROKEN') {
                    return;
                }
                let radarStatement = robot.radar_beams.map(
                    (b) => {
                        return `${b.dir}~${b.d}`;
                    }
                );
                let radarData = radarStatement.join(';');

                let close_stars = stars.sort((a,b) => distance(a.x, a.y, robot.x, robot.y) - distance(b.x, b.y, robot.x, robot.y));
                close_stars = close_stars.slice(0, 100);
                let spell = close_stars.filter(star => (star.death < 1)).map(star => {
                    let dx = star.x - robot.x;
                    let dy = star.y - robot.y;
                    let l = (dx * dx + dy * dy) ** 0.5;
                    dx /= l;
                    dy /= l;
                    let vx = Math.cos(robot.dir);
                    let vy = Math.sin(robot.dir);
                    let a = Math.acos(dx * vx + dy * vy);
                    if (dx * (-vy) + dy * vx < 0) {
                        a = -a;
                    }
                    return `${a}~${l}`;
                });

                let spellData = spell.join(',');
                worker.postMessage({
                    type: 'run_sql',
                    sql: theSQL.replace(/\*\*\*/g, radarData).replace(/\+\+\+/g, robot.memory).replace(/@@@/g, spellData)
                });
                desire_balance += 1;
            }
            function requestDesire() {
                let radarStatement = robot.radar_beams.map(
                    (b) => {
                        return `RadarBeam(direction: ${b.dir}, distance: ${b.d})`;
                    }
                );
                let radarProgram = radarStatement.join(';\n');

                let program =  radarProgram + ';\n' + theProgram;

                console.log('program:', program);
                worker.postMessage({
                    type: 'run_predicate', 
                    predicate: thePredicate, 
                    program: program,
                    hide_error: false
                });            
                backlogCount += 1;
                console.log('Backlog:', backlogCount);
            }
            let compilation_balance = 0;
            function digest() {
                if (regularized_desire_balance > 1.0 && regularized_desire_balance != 10) {
                  let output=document.getElementById('robot_output')
                  output.innerHTML = 'Robot not ready. Please try again in 10 seconds.';
                  return;
                }
                theProgram = 'SpellBearing(bearing: ToFloat64(ab[0]), distance:ToFloat64(ab[1])) :- x in Split(data, ","), ab = Split(x, "~"), SpellData(data); RadarBeam(direction: dir, distance: d) :- x in Split(data, ";"), ab = Split(x, "~"), dir == ToFloat64(ab[0]), d = ToFloat64(ab[1]), RadarData(data); RadarData("***"); Memory("+++"); SpellData("@@@");\n' + document.getElementById("robot_code").innerText;
                worker.postMessage({type: 'compile_predicate', predicate: thePredicate, program: theProgram})
                compilation_balance += 1;
            };
            var thePredicate = 'RobotDesire';
            var theProgram = 'RobotDesire(drive: 0, turn: 1);'
            var theSQL = 'BROKEN';
            var theTime = 0;
            worker.onmessage = function(event) {
                backlogCount -= 1;

                let worker_response = event.data;
                if (worker_response.get('type') == 'compile_predicate') {
                    console.log('Compiled SQL result:', worker_response);
                    if (worker_response.get('status') != 'OK') {
                        console.log('Calling output:', worker_response)
                        setTimeout(() => {console.log('Called here!'); outputResult(worker_response)}, 100);
                        //outputResult(worker_response);
                        theSQL = 'BROKEN';
                        if (worker_response.get('error_message') == 'Compiler is not ready.') {
                            compile_attempt += 1;
                            setTimeout(() => {console.log('Trying to compile again.'); digest();}, 100);
                            worker_response.set('error_message',
                                                worker_response.get('error_message') + ' / Attempt ' + compile_attempt)
                        }
                        return;
                    }
                    theSQL = worker_response.get('result');
                    console.log(theSQL);
                    compilation_balance -= 1;
                    return;
                }
                outputResult(worker_response);

                desire_balance -= 1;
                if (regularized_desire_balance > 1) {
                  regularized_desire_balance = desire_balance * 0.05 + regularized_desire_balance * 0.95;
                }
                if (desire_balance > desire_balance_threshold &&  // slow down if queue grows.
                    regularized_desire_balance > 1 /* once we've proven computer is fast, keep speed */) {
                  speed_of_time = speed_of_time * 0.9;
                  desire_balance_threshold = desire_balance + 2
                  console.log('Machine is too slow. Slowing to ', speed_of_time)
                  clearInterval(interval_of_timeStep);
                  clearInterval(interval_of_requestDesire2);
                  interval_of_timeStep = setInterval(timeStep, 5 / speed_of_time);
                  interval_of_requestDesire2 = setInterval(requestDesire2, 50 / speed_of_time);
                }
                if (desire_balance_threshold > 4) {
                    desire_balance_threshold -= 0.1;
                }

                let drive_turn_str = worker_response.get('result').split('\n')[3].split('|');
                let drive_turn = {drive: parseFloat(drive_turn_str[1]), turn: parseFloat(drive_turn_str[2])};
                robot.memory = drive_turn_str[3].substr(1, drive_turn_str[3].length - 2);

                robot.desire = drive_turn;
                let hide_error = worker_response.get('hide_error');
                let status = worker_response.get('status');
                let program = worker_response.get('program');
                let predicate = worker_response.get('predicate');
                lastProgramExecuted = program;
                lastPredicateExecuted = predicate;
                // document.getElementById('robot_output').innerText = worker_response.get('result');
                /*if (!hide_error || status == 'OK') {
                    outputResult(worker_response);
                }*/
            };
            function outputResult(execution_result) {
                let output=document.getElementById('robot_output')
                if (execution_result.get('status') == 'OK') {
                    output.innerHTML = execution_result.get('result');
                } else {
                    output.innerHTML = `
<u>Rule:</u><br/>
${execution_result.get('error_context')}


[ <div style="color:red;font-weight:bold;display:inline">Error</div> ] ${execution_result.get('error_message')}
`;
                    output.innerHTML = output.innerHTML.replaceAll('{logica error}-*', '<div style="color:yellow;font-weight:bold;display:inline">');
                    output.innerHTML = output.innerHTML.replaceAll('*-{logica error}', '</div>');
                
                }
            }

          </script>
        </div>
    </div>
    <script>

        let w = 800;
        let h = 800;
        document.getElementById('canvas').width = w;
        document.getElementById('canvas').height = w;

        let c = document.getElementById('canvas');
        let ctx = c.getContext('2d');

        var robot = {
            x: 0,
            y: 0,
            view_x: 0,
            view_y: 0,
            dir: 0,
            size: 10,
            speed: 1,
            radar_beams: [
                {dir: -0.4, d: 300, max_d: 300},
                {dir: -0.3, d: 300, max_d: 300},
                {dir: -0.2, d: 300, max_d: 300},
                {dir: -0.1, d: 300, max_d: 300},
                {dir: -0.0, d: 300, max_d: 300},
                {dir: 0.1, d: 300, max_d: 300},
                {dir: 0.2, d: 300, max_d: 300},
                {dir: 0.3, d: 300, max_d: 300},
                {dir: 0.4, d: 300, max_d: 300}
            ],
            forward: {x: 1, y: 0},
            desire: {drive: 0, turn: 1},
            memory: "Life is good!"
        };

        robot.radar_beams = [];
        for(let i = 0; i <= 200; i++) {
            robot.radar_beams.push({dir: (i - 100) / 200.0, d: 300, max_d: 300});
        }

        var trees = [
            /* {x: 300, y: 200, r: 50},
            {x: 250, y: 400, r: 30},
            {x: 100, y: 250, r: 20} */
        ];
        for(let i = 0; i < 6000; i++) {
            let tx = Math.random() * 8000 - 4000;
            let ty = Math.random() * 8000 - 4000;
            if (distance(tx, ty, robot.x, robot.y) < 300) {
                continue;
            }
            trees.push({x: tx, y: ty, r: Math.random() * 50});
        }

        var timeTicks = 0;
        var stars = [{x: 0, y: 0, death: 0, nature: {rotations: [{period: 15.0, speed: 2.0},
                                                             {period: 31.0, speed: 2.3}]}}];
        stars = [];
        function distanceToTree(x, y) {
            let d = 1000;
            trees.forEach(t => {
                d = Math.min(d, distance(t.x, t.y, x, y) - t.r);
            });
            return d;
        }
        function makeStars() {
            while(stars.length < 100) {
            // for(let i = 0; i < 200; i++) {
                let star = {
                    x: Math.random() * 8000 - 4000,
                    y: Math.random() * 8000 - 4000,
                    death: 0,
                    nature: {
                        rotations: [
                            {
                                period: 2 * Math.floor(Math.random() * 10),
                                speed: Math.random() * 5 - 2.5
                            },
                            {
                                period: 2 * Math.floor(Math.random() * 10),
                                speed: Math.random() * 5 - 2.5
                            },
                            {
                                period: 2 * Math.floor(Math.random() * 10),
                                speed: Math.random() * 5 - 2.5
                            }
                        ]
                    }
                }
                if (distanceToTree(star.x, star.y) > 20) {
                  stars.push(star);
                }
            }
        }

        function starPhysics() {
            stars.forEach((star) => {
                if (star.death > 0) {
                    star.death += 0.1;
                    star.x = robot.x;
                    star.y = robot.y;
                }
                if (star.death == 0 && distance(star.x, star.y, robot.x, robot.y) < 15) {
                    star.death = 1;
                }
            });
            stars = stars.filter((star) => (star.death < 60));
        }

        makeStars();
        function drawStars() {
            function starPoint(star, i) {
                let tau = 2 * 3.14;
                let angle = tau * i / 100.0;
                let radius = 10;
                star.nature.rotations.forEach((r) => {
                    radius += Math.sin(r.period * angle + timeTicks / 50.0 * r.speed) * 5;
                });
                if (star.death > 0) {
                    let death_phase = star.death / 10;
                    radius += Math.sin(death_phase) * 40;
                    if (radius < 0 || death_phase > 6.28 * 3/4.) {
                        radius = 0;
                    }
                }
                // radius *= 5;
                return {x: star.x + Math.cos(angle) * radius, y: star.y + Math.sin(angle) * radius};
            }
            stars.filter(s => (distance(s.x, s.y, robot.view_x, robot.view_y) < 1000)).forEach((s) => {
              ctx.beginPath();
              let p = starPoint(s, 0);
              ctx.moveTo(p.x, p.y);
              for(let i = 0; i <= 100; i++) {
                let p = starPoint(s, i);
                // console.log(p);
                ctx.lineTo(p.x, p.y);
              }
              ctx.strokeStyle = '#fff';
              ctx.fillStyle = '#ff0';
              ctx.globalAlpha = 0.8;
              ctx.strokeWidth = 2;
              ctx.stroke();
              ctx.fill();
            });
        }
        function drawTrees() {
            trees.forEach((t) => {
                ctx.beginPath();
                ctx.arc(t.x, t.y, t.r, 0, 6.28);
                ctx.fillStyle = '#373';
                ctx.fill();
            });
        }

        function blockedPoint(x, y) {
            return trees.some((t) => {
                if ((t.x - x) ** 2 + (t.y - y) ** 2 < t.r ** 2) {
                    return true;
                }
                return false;
            });
        }
        function drawRobot(robot) {
            let dir = robot.dir;
            let size = robot.size;
            let forward = robot.forward;
            let right = {x: Math.cos(dir + Math.PI / 2), y: Math.sin(dir + Math.PI / 2)};


            ctx.beginPath();
            ctx.moveTo(robot.x + forward.x * size / 2, robot.y + forward.y * size / 2);
            ctx.lineTo(robot.x + right.x * size, robot.y + right.y * size);
            ctx.lineTo(robot.x + right.x * size - 2 * forward.x * size, robot.y + right.y * size - 2 * forward.y * size);
            ctx.lineTo(robot.x - right.x * size - 2 * forward.x * size, robot.y - right.y * size - 2 * forward.y * size);
            ctx.lineTo(robot.x - right.x * size, robot.y - right.y * size);
            ctx.lineTo(robot.x + forward.x * size / 2, robot.y + forward.y * size / 2);
            ctx.fillStyle = '#500';
            ctx.strokeStyle = '#ffe'
            ctx.fill();
            ctx.stroke();
            robot.radar_beams.forEach((beam) => {
                ctx.beginPath();
                ctx.moveTo(robot.x, robot.y);
                ctx.lineTo(robot.x + Math.cos(robot.dir + beam.dir) * beam.d,
                           robot.y + Math.sin(robot.dir + beam.dir) * beam.d)
                //ctx.strokeStyle = '#eee';
                ctx.strokeStyle = '#666';
                ctx.globalAlpha = 0.5;
                //ctx.stroke();
            });

            ctx.beginPath();
            ctx.moveTo(robot.x, robot.y);
            robot.radar_beams.forEach((beam) => {
                ctx.lineTo(robot.x + Math.cos(robot.dir + beam.dir) * beam.d,
                           robot.y + Math.sin(robot.dir + beam.dir) * beam.d)
            });
            ctx.fillStyle= '#eee';
            ctx.globalAlpha = 0.5;
            ctx.fill();
        }

        ctx.save();
        function drawWorld() {
            timeTicks += 1;
            ctx.restore();
            ctx.save();
            ctx.fillRect(0, 0, w, h); 
            ctx.translate(w/2 - robot.view_x, h/2 - robot.view_y);
            ctx.fillStyle='#000000';
            // ctx.fillRect(0, 0, w, h); 
            //ctx.fillRect(-10000, -10000, 10000, 10000);
            drawRobot(robot);
            drawTrees();
            drawStars();
        }
        clip = (x, l, h) => (Math.min(Math.max(x, l), h));
        function robotDesire(robot) {
            // robot.dir += Math.random() * 0.1 - 0.05;  //0.01;
            // robot.forward = {x: Math.cos(robot.dir), y: Math.sin(robot.dir)};
            if (isNaN(robot.desire.drive) || isNaN(robot.desire.turn)) {
                return;
            }
            robot.speed = clip(robot.desire.drive, -5, 5);
            robot.dir += clip(robot.desire.turn, -5, 5) / 100;
        }
        
        function nearestPoint(line_x, line_y, line_dir, t_x, t_y) {
            let v_x = Math.cos(line_dir);
            let v_y = Math.sin(line_dir);
            let dx = t_x - line_x;
            let dy = t_y - line_y;
            let dl = (dx * dx + dy * dy) ** 0.5;
            let cos = (dx * v_x + dy * v_y) / dl;
            let np_x = line_x + v_x * dl * cos;
            let np_y = line_y + v_y * dl * cos;
            return {x: np_x, y: np_y, cos: cos};
        }
        function distance(x0, y0, x1, y1) {
            return ((x1 - x0) ** 2 + (y1 - y0) ** 2) ** 0.5;
        }
        function touchingPoint(line_x, line_y, line_dir, c_x, c_y, r) {
            let v_x = Math.cos(line_dir);
            let v_y = Math.sin(line_dir);
            let np = nearestPoint(line_x, line_y, line_dir, c_x, c_y);
            let d = distance(np.x, np.y, c_x, c_y);
            if (d > r || np.cos < 0) {
                return null;
            }
            let g = (r * r - d * d) ** 0.5;
            let tx = np.x - g * v_x;
            let ty = np.y - g * v_y;
            return {x: tx, y: ty};
        }
        function beamTouch(robot_dir, robot_x, robot_y, beam_dir, beam_max_d) {
          let true_dir = robot_dir + beam_dir;
          let v_x = Math.cos(true_dir);
          let v_y = Math.sin(true_dir);

          let t = {x: robot_x + v_x * beam_max_d, y: robot_y + v_y * beam_max_d};
          let d = beam_max_d;

          relevantTrees.forEach((t) => {
            let tp = touchingPoint(robot_x, robot_y, true_dir, t.x, t.y, t.r);
            if (tp === null) {
            } else {
              let this_d = distance(tp.x, tp.y, robot_x, robot_y);
              if (this_d < d) {
                t = tp;
                d = this_d;
              }
            }
          });
          return d;
        }

        var relevantTrees = null;

        function beamsUpdate(robot) {

            relevantTrees = [];
            trees.forEach((tree) => {
              if (distance(robot.x, robot.y, tree.x, tree.y) < 400) {
                relevantTrees.push(tree);
              }
            });
            // console.log('Num trees, relevant trees', trees.length, relevantTrees.length);

            robot.radar_beams.forEach((b) => {
                b.d = beamTouch(robot.dir, robot.x, robot.y, b.dir, b.max_d);
            });
        }
        function robotPhysics(robot) {
            let speed = robot.speed;
            let new_x = robot.x + robot.forward.x * speed;
            let new_y = robot.y + robot.forward.y * speed;
            let blocked = blockedPoint(new_x, new_y);            
            if (!blocked) {
                robot.x = new_x;
                robot.y = new_y;
            }
            if (distance(robot.x, robot.y, robot.view_x, robot.view_y) > 5) {
                let tx = robot.x + 10 * robot.forward.x * robot.speed;
                let ty = robot.y + 10 * robot.forward.y * robot.speed;
                let dx = (tx - robot.view_x);
                let dy = (ty - robot.view_y);
                let l = distance(tx, ty, robot.view_x, robot.view_y);
                dx /= l;
                dy /= l;
                let s = l / 30.;
                robot.view_x += s * dx;
                robot.view_y += s * dy;
            }
            beamsUpdate(robot);
        }
        function timeStep() {
            theTime += 1;
            robotPhysics(robot);
            drawWorld();
            robotDesire(robot);
            starPhysics();
            // robot.dir += 0.001;
            robot.forward = {x: Math.cos(robot.dir), y: Math.sin(robot.dir)};
        }

        function copyProgram() {
            let url = new URL(window.location.href);
            url.searchParams.set('program', document.getElementById('robot_code').innerText);
            setTimeout(() => {window.history.replaceState(null, null, url.toString())}, 700);
            flipIt();
            navigator.clipboard.writeText(url.toString());
        }
        function GetProgramFromURL() {
            let program_from_url = new URLSearchParams(window.location.search).get('program');
            if (program_from_url !== null && program_from_url.length > 0) {
                document.getElementById('robot_code').innerHTML = program_from_url;
            }
        }
        GetProgramFromURL();

        function flipIt() {
            let p = document.getElementById('robot_code');
            p.classList.add('animated_flip');
            setTimeout(() => {p.classList.remove('animated_flip');}, 1000);
        }

        let speed_of_time = 1.0;
        let desire_balance_threshold = 4;
        let compile_attempt = 0;
        setTimeout(digest, 1000);
        let interval_of_timeStep = setInterval(timeStep, 5);
        let interval_of_requestDesire2 = setInterval(requestDesire2, 50);

    </script>
</body>    
</html>